package com.example.chat.service.Impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.AES;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.chat.domain.dto.GroupInviteDTO;
import com.example.chat.domain.dto.GroupInviteYON;
import com.example.chat.domain.entity.*;
import com.example.chat.domain.vo.*;
import com.example.chat.mapper.GroupChatMapper;
import com.example.chat.mapper.GroupInviteMapper;
import com.example.chat.mapper.GroupUserMapper;
import com.example.chat.result.Result;
import com.example.chat.service.GroupChatService;
import com.example.chat.service.GroupInviteService;
import com.example.chat.service.GroupUserService;
import com.example.chat.service.UserService;
import com.example.chat.utils.RedisConstants;
import com.example.chat.utils.SystemConstants;
import com.example.chat.utils.UserHolder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class GroupChatServiceImpl extends ServiceImpl<GroupChatMapper, GroupChat> implements GroupChatService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private GroupUserService groupUserService;
    @Resource
    private GroupChatMapper groupChatMapper;
    @Resource
    private GroupUserMapper groupUserMapper;
    @Resource
    private UserService userService;
    @Resource
    private GroupInviteService groupInviteService;
    @Resource
    private GroupInviteMapper groupInviteMapper;

    private static final DefaultRedisScript<Void> test = new DefaultRedisScript<>();
    private static final DefaultRedisScript<Void> del = new DefaultRedisScript<>();
    private static final DefaultRedisScript<Void> rem = new DefaultRedisScript<>();

    static {
        test.setLocation(new ClassPathResource("scripts/createGroup.lua"));
        test.setResultType(Void.class);
        del.setLocation(new ClassPathResource("scripts/deleteGroupChatList.lua"));
        del.setResultType(Void.class);
        rem.setLocation(new ClassPathResource("scripts/removeGroupChat.lua"));
        rem.setResultType(Void.class);
    }

    @Override
    public Result createGroupChat(List<Long> ids,String groupName) {
        Long currentId = UserHolder.getCurrentId();
        //1.向集合第一个位置中插入自己本身
        ids.add(0,currentId);
        //2.向群聊表创建一条记录
        GroupChat groupChat = GroupChat.builder()
                .icon(SystemConstants.GROUPCHAT_ICON)
                .groupName(groupName)
                .groupId(
                        RedisConstants.BEGIN_TIMESTAMP +
                                stringRedisTemplate.opsForValue().increment("chat_group:group_number:")
                )
                .peopleNumber(ids.size())
                .build();
        boolean save = save(groupChat);
        Long groupNumber = groupChat.getGroupId();
        if(save){
            //3.创建成功后，向群用户表插入数据
            List<GroupUser> groupUserList = new ArrayList<>();
            String idStr = StrUtil.join(",", ids);
            List<User> userList = userService.lambdaQuery().in(User::getId, ids)
                    .last("order by field(id," + idStr + ")").list();
            for (int i = 0; i < ids.size(); i++) {
                GroupUser groupUser = GroupUser.builder()
                        .groupChatId(groupNumber)
                        .userId(ids.get(i))
                        .groupNickName(userList.get(i).getUserName())
                        .build();
                groupUser.setRole(i == 0 ? 0 : 2);
                groupUserList.add(groupUser);
            }
            boolean saveBatch = groupUserService.saveBatch(groupUserList);
            //4.插入成功后，向redis中添加相关数据
            if(saveBatch){
                String time = LocalDateTime.now()
                        .format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
                String timestamp = String.valueOf(System.currentTimeMillis());
                String timestamp2 = String.valueOf(System.currentTimeMillis()+1);
                String content = "";
                String tip = "0";
                List<String> keys = ids.stream().map(String::valueOf).collect(Collectors.toList());
                List<String> args = List.of(groupNumber.toString(),timestamp,timestamp2,content,tip,time);
                stringRedisTemplate.execute(test,keys,args.toArray());
            }
        }
        return Result.ok();
    }

    @Override
    public Result queryGroupChats() {
        Long currentId = UserHolder.getCurrentId();
        List<QueryGroupVo> groupVoList = groupChatMapper.queryGroupChats(currentId);
        return Result.ok(groupVoList);
    }

    @Override
    public Result queryGroupList() {
        Long currentId = UserHolder.getCurrentId();
        Set<String> idSet = stringRedisTemplate.opsForZSet().reverseRange(RedisConstants.CHAT_GROUP_LIST + currentId, 0, -1);
        if(idSet == null || idSet.size() < 1){
            return Result.ok(Collections.emptyList());
        }

        List<Long> ids = idSet.stream().map(Long::valueOf).collect(Collectors.toList());
        String idStr = StrUtil.join(",",ids);
        List<GroupChat> groupChatList = lambdaQuery()
                .in(GroupChat::getGroupId, ids).last("order by field(group_id," + idStr + ")").list();
        List<QueryChatListVo> chatListItemVos = new ArrayList<>();
        for (GroupChat groupChat : groupChatList) {
            QueryChatListVo queryChatListVo = QueryChatListVo.builder()
                    .id(groupChat.getGroupId().toString())
                    .icon(groupChat.getIcon())
                    .userName(groupChat.getGroupName())
                    .type(2)
                    .build();
            chatListItemVos.add(queryChatListVo);
        }

        for (int i = 0; i < ids.size(); i++) {
            Map<Object, Object> objectObjectMap = stringRedisTemplate.opsForHash()
                    .entries(RedisConstants.CHAT_GROUP_OBJECT + currentId + ":" + ids.get(i));
            ChatObjectRedis chatObjectRedis = BeanUtil.toBean(objectObjectMap, ChatObjectRedis.class);
            BeanUtil.copyProperties(chatObjectRedis,chatListItemVos.get(i));
        }
        return Result.ok(chatListItemVos);
    }

    @Override
    public Result queryGroupContents(Long id, Long timestamp, Integer offset) {
        Long currentId = UserHolder.getCurrentId();
        QueryContentVo queryContentVo = new QueryContentVo();
        Integer queryCount = 20;

        //1.查询群聊信息
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(RedisConstants.CHAT_GROUP_MESSAGE + id,
                        0, timestamp, offset, queryCount);
        if(typedTuples == null || typedTuples.isEmpty()){
            queryContentVo.setRemain(false);
            return Result.ok(queryContentVo);
        }
        List<Long> msgIds = new ArrayList<>();
        Long tempTimastamp = 0L;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            msgIds.add(Long.valueOf(typedTuple.getValue()));
            tempTimastamp = typedTuple.getScore().longValue();
        }
        //设置最小时间戳,和是否还有剩余聊天记录
        queryContentVo.setRemain(queryCount.equals(msgIds.size()));
        queryContentVo.setTimestamp(tempTimastamp);

        //2.查询数据库表,user表获得对应信息(信息id,用户userId,用户icon，text,信息time,发送者是否是本人type)
        List<ChatContent> chatContentList = groupChatMapper.queryGroupMessage(msgIds);
        for (ChatContent chatContent : chatContentList) {
            chatContent.setType(chatContent.getUserId().equals(currentId) ? 2 : 1);
            //消息解密
            byte[] decrypt = AES.decrypt(Base64.getDecoder().decode(chatContent.getText()), SystemConstants.AES_KEY.getBytes(StandardCharsets.UTF_8));
            String text = new String(decrypt, StandardCharsets.UTF_8);
            chatContent.setText(text);
        }
        queryContentVo.setContents(chatContentList);
        //3.返回结果
        return Result.ok(queryContentVo);
    }

    @Override
    public Result tipClearOrReduce(Long id, Integer type) {
        Long currentId = UserHolder.getCurrentId();
        if(Integer.valueOf(0).equals(type)){
            stringRedisTemplate.opsForHash()
                    .put(RedisConstants.CHAT_GROUP_OBJECT+currentId+":"+id,
                            RedisConstants.CHAT_OBJECT_Tip,"0");
//            log.info("清空tip值");
            return Result.ok();
        }
        stringRedisTemplate.opsForHash()
                .increment(RedisConstants.CHAT_GROUP_OBJECT+currentId+":"+id,RedisConstants.CHAT_OBJECT_Tip,-1);
//        log.info("tip值减一");
        return Result.ok();
    }

    @Transactional
    public Result deleteById(Long id) {
        boolean isRemove = remove(new QueryWrapper<GroupChat>().lambda().eq(GroupChat::getGroupId, id));
        if(isRemove){
            boolean isRemove2 = groupUserService.remove(new QueryWrapper<GroupUser>()
                    .lambda().eq(GroupUser::getGroupChatId, id));
            if(isRemove2){
                Set<String> stringSet = stringRedisTemplate.opsForZSet()
                        .range(RedisConstants.CHAT_GROUP_USERS_ID+id, 0, -1);
                List<String> ids = stringSet.stream().map(String::valueOf).collect(Collectors.toList());
                List<String> args = List.of(id.toString());
                stringRedisTemplate.execute(rem,ids,args.toArray());
            }
        }
        return Result.ok();
    }

    @Transactional
    public Result leaveById(Long id) {
        Long currentId = UserHolder.getCurrentId();
        boolean isRemove = groupUserService.remove(new QueryWrapper<GroupUser>()
                .lambda().eq(GroupUser::getGroupChatId, id).eq(GroupUser::getUserId,currentId));
        if(isRemove){
            boolean success = lambdaUpdate()
                    .eq(GroupChat::getGroupId, id)
                    .setSql("people_number = people_number - 1")
                    .update();
            if(success){
                    stringRedisTemplate.delete(RedisConstants.CHAT_GROUP_USERS_INFO+id);
                    stringRedisTemplate.opsForZSet().remove(RedisConstants.CHAT_GROUP_USERS_ID+id,currentId.toString());
                    stringRedisTemplate.opsForZSet().remove(RedisConstants.CHAT_GROUP_LIST+currentId,id.toString());
                    stringRedisTemplate.delete(RedisConstants.CHAT_GROUP_OBJECT+currentId+":"+id);
            }
        }
        return Result.ok();
    }

    @Override
    public Result getGroupInfo(Long id) {
        GroupChat groupChat = lambdaQuery().eq(GroupChat::getGroupId, id).one();
        QueryGroupInfoVo queryGroupInfoVo = BeanUtil.toBean(groupChat, QueryGroupInfoVo.class);
        return Result.ok(queryGroupInfoVo);
    }

    @Transactional
    public Result deleteChatListById(Long id) {
        Long currentId = UserHolder.getCurrentId();
        List<String> keys = List.of(currentId.toString(),id.toString());
        stringRedisTemplate.execute(del,keys);
        return Result.ok();
    }

    @Override
    public Result queryGroupFulInfo(Long id) {
        Long currentId = UserHolder.getCurrentId();
        QueryGroupFulInfoVo queryGroupFulInfoVo = groupChatMapper.queryGroupFulInfo(currentId, id);
        List<GroupUserVo> groupUserVos;
        String groupUsers = stringRedisTemplate.opsForValue().get(RedisConstants.CHAT_GROUP_USERS_INFO + id);
        if(StrUtil.isEmptyIfStr(groupUsers)){
            groupUserVos = groupUserMapper.queryGroupUsers(id);
            stringRedisTemplate.opsForValue().set(RedisConstants.CHAT_GROUP_USERS_INFO+id,
                    JSONUtil.toJsonStr(groupUserVos));
            stringRedisTemplate.expire(RedisConstants.CHAT_GROUP_USERS_INFO+id,
                    RedisConstants.TIME_THIRTY_MINUTES, TimeUnit.MINUTES);
        }else{
            groupUserVos = JSONUtil.toList(groupUsers, GroupUserVo.class);
        }
        queryGroupFulInfoVo.setGroupUsers(groupUserVos);
        return Result.ok(queryGroupFulInfoVo);
    }

    @Override
    public Result inviteUser(GroupInviteDTO groupInviteDTO) {
        List<Long> invitees = groupInviteDTO.getInvitees();
        List<GroupInvite> list = new ArrayList<>();
        for (Long invitee : invitees) {
            GroupInvite groupInvite = GroupInvite.builder()
                    .groupId(groupInviteDTO.getGroupId())
                    .inviter(UserHolder.getCurrentId())
                    .invitee(invitee)
                    .build();
            list.add(groupInvite);
        }
        boolean save = groupInviteService.saveBatch(list);
        if(save){
            return Result.ok();
        }
        return Result.fail("添加失败");
    }

    @Override
    public Result getInvitees(Long groupId) {
        List<GroupInvite> list = groupInviteService.lambdaQuery()
                .eq(GroupInvite::getStatus, 2)
                .eq(GroupInvite::getGroupId, groupId)
                .select(GroupInvite::getInvitee).list();
        List<Long> invitees = list.stream().map(GroupInvite::getInvitee).collect(Collectors.toList());
        return Result.ok(invitees);
    }

    @Transactional
    @Override
    public Result inviteByType(GroupInviteYON groupInviteYON) {
        Integer type = groupInviteYON.getType();
        Long groupId = groupInviteYON.getGroupId();
        Long currentId = UserHolder.getCurrentId();
        //同意加群
        if(type == 1){
            boolean update = groupInviteService.lambdaUpdate()
                    .eq(GroupInvite::getGroupId, groupId)
                    .eq(GroupInvite::getInvitee, currentId)
                    .eq(GroupInvite::getInviter,groupInviteYON.getInviter())
                    .set(GroupInvite::getStatus, 1)
                    .update();
            if(update){
                //修改群用户表，添加一员
                User user = userService.getById(currentId);
                GroupUser groupUser = GroupUser
                        .builder()
                        .userId(currentId)
                        .groupChatId(groupId)
                        .groupNickName(user.getUserName())
                        .role(2)
                        .build();
                boolean save = groupUserService.save(groupUser);
                //修改群聊表，人数加一
                GroupChat groupChat = GroupChat.builder()
                        .build();
                boolean update1 = lambdaUpdate()
                        .eq(GroupChat::getGroupId, groupId)
                        .setSql("people_number = people_number + 1")
                        .update();
                if(save && update1){
                    stringRedisTemplate.opsForZSet().add(RedisConstants.CHAT_GROUP_USERS_ID+groupId.toString()
                            ,currentId.toString(),System.currentTimeMillis());
                    stringRedisTemplate.delete(RedisConstants.CHAT_GROUP_USERS_INFO+groupId);
                    return Result.ok();
                }
                return Result.fail("验证操作部分失败");
            }

        }else {
            //拒绝加群
            boolean update = groupInviteService.lambdaUpdate()
                    .eq(GroupInvite::getGroupId, groupInviteYON.getGroupId())
                    .eq(GroupInvite::getInvitee, currentId)
                    .eq(GroupInvite::getInviter,groupInviteYON.getInviter())
                    .set(GroupInvite::getStatus, 0)
                    .update();
            if(update){
                return Result.ok();
            }
        }
        return Result.fail("验证操作失败");
    }

    @Override
    public Result getListInvite() {
        List<GroupInviteDetailVo> listInvite = groupInviteMapper.getListInvite(UserHolder.getCurrentId());
        return Result.ok(listInvite);
    }
}
